﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;

namespace KATS_Client.Client
{
    /// <summary>
    /// The different actions the client can perform with the server
    /// </summary>
    public static class FileActions
    {
        /// <summary>
        /// This function retrieves a file from the server and saves it locally
        /// </summary>
        /// <param name="RemoteFileLocation">The remote file location</param>
        /// <param name="LocalFileLocation">The local file location</param>
        /// <returns>Bool indicating weither transfer was successful or not</returns>
        private static bool GetFile(string RemoteFileLocation, string LocalFileLocation)
        {
            FileInfo RemoteLocation = new FileInfo(RemoteFileLocation);
            FileInfo LocalLocation = new FileInfo(LocalFileLocation);
            FileInfo LocalLocation_Temp = new FileInfo(LocalFileLocation + ".part");

            try
            {
                ServerFunctions.SetLocation(RemoteFileLocation);

                if (ServerFunctions.Exists() == true)
                {
                    long fileSize = ServerFunctions.FileLength(RemoteFileLocation);

                    ServerFunctions.OpenReader();

                    // set up all the variables required to monitor the file transfer
                    int i = 0;
                    int loopsBeforePercentageChangeEvent = (int)(fileSize / 100 / Properties.Settings.Default.ChunkSize); // every 1 percent
                    long sizeOfOnePercent = fileSize / 100;
                    DateTime startTime = DateTime.Now;
                    DateTime currentTime = DateTime.Now.AddMilliseconds(100.0);
                    DateTime dt1;
                    DateTime dt2 = startTime;
                    DateTime dt3 = currentTime;
                    //Console.WriteLine("Number of loops before update: {0}", loopsBeforePercentageChangeEvent);
                    FileActionsQueue.Trigger_Transfer_Update(0, string.Empty);
                    double average_speed = 0; // object to maintain final average speed

                    int bufferSize = 50 * 1024 * 1024;
                    if (bufferSize > fileSize)
                        bufferSize = (int)fileSize + 1;

                    // file output code
                    BufferedFileWrite fileWrite = new BufferedFileWrite(bufferSize, new BinaryWriter(LocalLocation_Temp.OpenWrite()));
                    long pos = 0;
                    while (pos < fileSize)
                    {
                        // get the file and check the checksum
                        byte[] fileChunk = ServerFunctions.GetFileChunk();

                        pos += fileChunk.Length;
                        fileWrite.Write(fileChunk); // write the chunk to the memory stream


                        // update the progress text
                        i++;
                        if (i == loopsBeforePercentageChangeEvent || pos == fileSize)
                        {
                            // this is triggered after every 1 percent [ie, update the display every 1 percent]
                            i = 0; // reset counter
                            int percentageTransfered = (int)(100 * pos / fileSize);

                            // calculate the average speed
                            currentTime = DateTime.Now;
                            double timeInMilliSecondsSinceStart = (currentTime - startTime).TotalMilliseconds;
                            average_speed = pos / (timeInMilliSecondsSinceStart / 1000);

                            // calculate the current speed
                            dt1 = dt2;
                            dt2 = dt3;
                            dt3 = currentTime;
                            double timeInMilliseconds = (dt3 - dt1).TotalMilliseconds;
                            double speed = 2 * sizeOfOnePercent / (timeInMilliseconds / 1000);

                            // update the displayed speeds
                            string s = "" + (int)(speed / 1024) + "KB/s";
                            FileActionsQueue.Trigger_Transfer_Update(percentageTransfered, s);
                        }


                        // now loop again
                    }
                    ServerFunctions.CloseReader();

                    fileWrite.Flush();
                    fileWrite.Dispose();
                    fileWrite = null;


                    // rename file to proper name
                    LocalLocation_Temp.MoveTo(LocalLocation.FullName);

                    string t = "" + (int)(average_speed / 1024) + "KB/s";
                    FileActionsQueue.Trigger_Transfer_Update(100, t);

                    return true;
                }
                else
                {
                    return true;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR [Client.FileActions.GetFile]: " + e.Message);
                try
                {
                    Console.WriteLine("Attempting to close file reader...");
                    ServerFunctions.CloseReader();
                }
                catch (Exception)
                {
                    Console.WriteLine("ERROR [Client.FileActions.GetFile]: " + e.Message);
                    Console.WriteLine("\tFailed to close the reader, if it was open");
                }
            }


            return false;
        }

        /// <summary>
        /// Is this a valid filename?
        /// </summary>
        /// <param name="testName"></param>
        /// <returns>True if valid, false otherwise</returns>
        public static bool IsValidFilename(string testName)
        {
            foreach (char c in Path.GetInvalidFileNameChars())
            {
                if (testName.Contains(c))
                {
                    return false;
                }
            }
            foreach (char c in Path.GetInvalidPathChars())
            {
                if (testName.Contains(c))
                {
                    return false;
                }
            }

            return true;
        }


        /// <summary>
        /// Sends a file to the host
        /// </summary>
        /// <param name="LocalFileLocation">The full path on the local system of the file</param>
        /// <param name="RemoteFileLocation">The full path on the server for the file</param>
        /// <returns>If action was succesful or not</returns>
        private static bool SendFile(string LocalFileLocation, string RemoteFileLocation)
        {
            try
            {
                int fileParts = 0;
                FileInfo LocalFile = new FileInfo(LocalFileLocation);
                FileInfo RemoteFile = new FileInfo(RemoteFileLocation);
                FileInfo RemoteFile_Temp = new FileInfo(RemoteFileLocation + ".part");
                // send file to the server 'host'

                ServerFunctions.SetLocation(RemoteFile.FullName);
                if (ServerFunctions.Exists() == true)
                {
                    // if there is already a file here, delete it
                    ServerFunctions.Delete();
                }

                ServerFunctions.SetLocation(RemoteFile_Temp.FullName);
                if (ServerFunctions.Exists() == true)
                {
                    // if there is already a file here, delete it
                    ServerFunctions.Delete();
                }

                long fileSize = LocalFile.Length;
                using (BinaryReader br = new BinaryReader(LocalFile.OpenRead()))
                {
                    ServerFunctions.OpenWriter();

                    // set up all the variables required to monitor the file transfer
                    int i = 0;
                    int loopsBeforePercentageChangeEvent = (int)(fileSize / 100 / Properties.Settings.Default.ChunkSize); // every 1 percent
                    long sizeOfOnePercent = fileSize / 100;
                    DateTime startTime = DateTime.Now;
                    DateTime currentTime = DateTime.Now.AddMilliseconds(100.0);
                    DateTime dt1;
                    DateTime dt2 = startTime;
                    DateTime dt3 = currentTime;

                    FileActionsQueue.Trigger_Transfer_Update(0, string.Empty);

                    double average_speed = 0; // object to maintain final average speed

                    while (br.BaseStream.Position < br.BaseStream.Length)
                    {
                        fileParts++;
                        byte[] fileData = br.ReadBytes(Properties.Settings.Default.ChunkSize);
                        while (ServerFunctions.SendFileChunk(fileData) == false)
                        {
                            // loop until function returns true
                            // [ie until the host receieves the file correctly]
                        }


                        // update the progress text
                        i++;
                        if (i == loopsBeforePercentageChangeEvent || br.BaseStream.Position == br.BaseStream.Length)
                        {
                            // this is triggered after every 1 percent [ie, update the display every 1 percent]
                            i = 0; // reset counter

                            int percentageTransfered = (int)(100 * br.BaseStream.Position / fileSize);

                            // calculate the average speed
                            currentTime = DateTime.Now;
                            double timeInMilliSecondsSinceStart = (currentTime - startTime).TotalMilliseconds;
                            average_speed = br.BaseStream.Position / (timeInMilliSecondsSinceStart / 1000);

                            // calculate the current speed
                            dt1 = dt2;
                            dt2 = dt3;
                            dt3 = currentTime;
                            double timeInMilliseconds = (dt3 - dt1).TotalMilliseconds;
                            double speed = 2 * sizeOfOnePercent / (timeInMilliseconds / 1000);

                            // update the displayed speeds
                            string s = "" + (int)(speed / 1024) + "KB/s";
                            FileActionsQueue.Trigger_Transfer_Update(percentageTransfered, s);
                        }

                    }
                    ServerFunctions.CloseWriter();
                }
                // rename remote file to new name
                ServerFunctions.Move(RemoteFile.FullName);


                //Console.WriteLine("File '{1}' transfered in {0} transfers.", fileParts, LocalFile.FullName);
                return true;
            }
            catch (FileNotFoundException)
            {
                // file has been locally deleted, so no need to worry about sending it, as its now impossible
                return true;
            }
            catch (IOException)
            {
                // file is currently being used by another process, wait to gain access to the file...
                return false;
            }
            finally
            {
                ServerFunctions.CloseWriter();
            }

        }

        /// <summary>
        /// Copy an file
        /// </summary>
        /// <param name="FileLocation">Source File</param>
        /// <param name="NewFileLocation">Target File</param>
        /// <returns>If successful</returns>
        public static bool CopyFile(FileSystemObject FileLocation, FileSystemObject NewFileLocation)
        {
            if (FileLocation.Equals(NewFileLocation))
            {
                // source and target are the same
                // do not copy
                return true;
            }

            if (FileLocation.IsRemote && NewFileLocation.IsRemote)
            {
                // both source and target are on the server

                // copy file on server
                ServerFunctions.SetLocation(FileLocation.Location);
                return ServerFunctions.Copy(NewFileLocation.Location);
            }
            else if (FileLocation.IsRemote && NewFileLocation.IsLocal)
            {
                // move from remote to local
                if (FileLocation.objectType == FileSystemObjectTypes.File)
                {
                    return GetFile(FileLocation.Location, NewFileLocation.Location);
                }
            }
            else if (FileLocation.IsLocal && NewFileLocation.IsRemote)
            {
                // move from local to remote
                if (FileLocation.objectType == FileSystemObjectTypes.File)
                {
                    return SendFile(FileLocation.Location, NewFileLocation.Location);
                }
            }
            else
            {
                // copy file locally
                try
                {
                    // only try to copy the file if the file exists
                    if (FileLocation.objectType == FileSystemObjectTypes.Directory)
                    {
                        DirectoryInfo directory = new DirectoryInfo(FileLocation.Location);
                        if (directory.Exists)
                        {
                            // the 'file' is actually a directory, copy the directory
                            CopyAFolder(new DirectoryInfo(FileLocation.Location), new DirectoryInfo(NewFileLocation.Location));
                        }
                    }
                    else
                    {
                        FileInfo file = new FileInfo(FileLocation.Location);
                        if (file.Exists == true)
                        {
                            // must be a single file
                            CopyAFile(new FileInfo(FileLocation.Location), new FileInfo(NewFileLocation.Location));
                        }
                    }
                    return true;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Error: {0}", e.Message);
                }
                return false;
            }
            return false;
        }

        /// <summary>
        /// Move a file system object
        /// </summary>
        /// <param name="FileLocation">Source</param>
        /// <param name="NewFileLocation">Target</param>
        /// <returns>Successful?</returns>
        public static bool Move(FileSystemObject FileLocation, FileSystemObject NewFileLocation)
        {
            if (FileLocation.Equals(NewFileLocation))
            {
                // source and target are the same
                // do not move
                return true;
            }

            if (FileLocation.IsRemote && NewFileLocation.IsRemote)
            {
                // perform on server
                if (ServerFunctions.SetLocation(FileLocation.Location))
                {
                    return ServerFunctions.Move(NewFileLocation.Location);
                }
                return true;
            }
            else if (FileLocation.IsRemote && NewFileLocation.IsLocal)
            {
                // remote to local
                if (FileLocation.objectType == FileSystemObjectTypes.Directory)
                {
                    // move directory
                    return MoveFolderToComputer(FileLocation, NewFileLocation);
                }
                else if (FileLocation.objectType == FileSystemObjectTypes.File)
                {
                    // move file
                    return MoveFileToComputer(FileLocation, NewFileLocation);
                }
            }
            else if (FileLocation.IsLocal && NewFileLocation.IsRemote)
            {
                // local to remote
                if (FileLocation.objectType == FileSystemObjectTypes.Directory)
                {
                    // move directory
                    return MoveFolderToServer(FileLocation, NewFileLocation);
                }
                else if (FileLocation.objectType == FileSystemObjectTypes.File)
                {
                    // move file
                    return MoveFileToServer(FileLocation, NewFileLocation);
                }
            }
            else
            {
                // perform on local computer
                try
                {
                    if (FileLocation.objectType == FileSystemObjectTypes.Directory)
                    {
                        // the 'file' is actually a directory, copy folder
                        MoveAFolder(new DirectoryInfo(FileLocation.Location), new DirectoryInfo(NewFileLocation.Location));
                    }
                    else if (FileLocation.objectType == FileSystemObjectTypes.File)
                    {
                        // only try to do the action if file exists
                        // must be a single file
                        MoveAFile(new FileInfo(FileLocation.Location), new FileInfo(NewFileLocation.Location));
                    }
                    return true;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Error [FileActions.Move()]: {0}", e.Message);
                }
                return false;
            }
            return false;
        }

        /// <summary>
        /// Delete a file system object
        /// </summary>
        /// <param name="Location">The object to delete</param>
        /// <returns>Successful?</returns>
        public static bool Delete(FileSystemObject Location)
        {
            if (Location.IsRemote)
            {
                // perform on server
                ServerFunctions.SetLocation(Location.Location);
                return ServerFunctions.Delete();
            }
            else
            {
                // on the local computer
                try
                {
                    if (Location.objectType == FileSystemObjectTypes.Directory)
                    {
                        // then is a directory
                        DirectoryInfo di = new DirectoryInfo(Location.Location);
                        if (di.Exists)
                        {
                            di.Delete(true);
                        }
                    }
                    else if (Location.objectType == FileSystemObjectTypes.File)
                    {
                        // then is a file
                        FileInfo file = new FileInfo(Location.Location);
                        if (file.Exists)
                            file.Delete();
                    }
                    return true;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Error [FileActions.Delete()]: '{0}'", e.ToString());
                }
                return false;
            }
        }

        /// <summary>
        /// Sends file, then deletes it if transfer successful
        /// </summary>
        /// <param name="LocalFileLocation">The local file location</param>
        /// <param name="RemoteFileLocation">The remote file target location</param>
        /// <returns>Operation successfull?</returns>
        private static bool MoveFileToServer(FileSystemObject LocalFileLocation, FileSystemObject RemoteFileLocation)
        {
            bool FileSent = SendFile(LocalFileLocation.Location, RemoteFileLocation.Location);

            try
            {
                if (FileSent == true)
                {
                    // file transfered successfully
                    FileInfo fi = new FileInfo(LocalFileLocation.Location);
                    if (fi.Exists)
                        fi.Delete();
                    return true;
                }
                else
                {
                    // file not transfer successfully
                    return false;
                }
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Move a folder to the server
        /// </summary>
        /// <param name="Location">Folder to move</param>
        /// <param name="remoteLocation">Folder to move folder into</param>
        /// <returns>Successful?</returns>
        private static bool MoveFolderToServer(FileSystemObject Location, FileSystemObject remoteLocation)
        {
            try
            {
                DirectoryInfo di = new DirectoryInfo(Location.Location);
                CreateDirectory(remoteLocation);

                string remoteLoc = remoteLocation.Location.TrimEnd(new char[] { '\\', '/' });

                foreach (DirectoryInfo folder in di.GetDirectories())
                {
                    // send other folders to server
                    FileSystemObject fso = new FileSystemObject(folder.FullName, false, FileSystemObjectTypes.Directory);
                    FileSystemObject fso2 = new FileSystemObject(remoteLoc + "\\" + folder.Name, true, FileSystemObjectTypes.Directory);

                    MoveFolderToServer(fso, fso2);
                }

                foreach (FileInfo file in di.GetFiles())
                {
                    // send files to server
                    FileSystemObject fso = new FileSystemObject(file.FullName, false, FileSystemObjectTypes.File);
                    FileSystemObject fso2 = new FileSystemObject(remoteLoc + "\\" + file.Name, true, FileSystemObjectTypes.File);

                    MoveFileToServer(fso, fso2);
                }
                di.Delete(true);
                return true;
            }
            catch (IOException)
            {
                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Move file to the local computer
        /// </summary>
        /// <param name="RemoteFileName">The remote file</param>
        /// <param name="LocalFileName">The local file</param>
        /// <returns>Successful?</returns>
        private static bool MoveFileToComputer(FileSystemObject RemoteFile, FileSystemObject LocalFile)
        {
            bool gettingFile = GetFile(RemoteFile.Location, LocalFile.Location);
            try
            {
                if (gettingFile == true)
                {
                    // file transfered succesfully from server
                    return Delete(RemoteFile);
                }
                else
                {
                    // file not transfered successfully
                    return false;
                }
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Move a folder to the local computer
        /// </summary>
        /// <param name="RemoteFolder">The remote folder</param>
        /// <param name="LocalFolder">The new containing folder</param>
        /// <returns>Successful?</returns>
        private static bool MoveFolderToComputer(FileSystemObject RemoteFolder, FileSystemObject LocalFolder)
        {
            string LocalFolderLoc = LocalFolder.Location.TrimEnd(new char[] { '\\', '/' });

            DirectoryInfo localFolder = new DirectoryInfo(LocalFolderLoc);
            if (!localFolder.Exists) // create the folder if it does not exist
                localFolder.Create();

            string[] RemoteSubDirectory = Directories(RemoteFolder);

            foreach (string s in RemoteSubDirectory)
            {
                DirectoryInfo di = new DirectoryInfo(s);

                FileSystemObject fso = new FileSystemObject(s, true, FileSystemObjectTypes.Directory);
                FileSystemObject fso2 = new FileSystemObject(LocalFolder + "\\" + di.Name, false, FileSystemObjectTypes.Directory);

                MoveFolderToComputer(fso, fso2);
            }

            string[] Remotefiles = Files(RemoteFolder);

            // queue the files in the current folder for retrieval
            foreach (string file in Remotefiles)
            {
                FileInfo fi = new FileInfo(file);

                FileSystemObject fso = new FileSystemObject(file, true, FileSystemObjectTypes.File);
                FileSystemObject fso2 = new FileSystemObject(LocalFolder.Location + "\\" + fi.Name, false, FileSystemObjectTypes.File);

                MoveFileToComputer(fso, fso2);
            }

            Delete(RemoteFolder);
            return true;
        }


        /// <summary>
        /// Copy a folder on the local computer
        /// </summary>
        /// <param name="from">Source</param>
        /// <param name="to">Target</param>
        private static void CopyAFolder(System.IO.DirectoryInfo from, System.IO.DirectoryInfo to)
        {
            // copy the directory and any subfolders
            // get subdirectorys to copy
            if (!to.Exists)
            {
                // create the target subdirectory if required
                to.Create();
            }
            System.IO.DirectoryInfo[] subFolders = from.GetDirectories();
            foreach (System.IO.DirectoryInfo subFolder in subFolders)
            {
                // copy the subfolders
                System.IO.DirectoryInfo subFrom = subFolder;
                System.IO.DirectoryInfo subTo = new System.IO.DirectoryInfo(string.Format("{0}\\{1}", to.FullName, subFrom.Name));
                //Console.WriteLine("Copying Folder\n->From: {0}\n->To: {1}", subFrom.FullName, subTo.FullName);
                CopyAFolder(subFrom, subTo);
            }
            // get array of files to copy
            System.IO.FileInfo[] from_files = from.GetFiles();
            foreach (System.IO.FileInfo from_file in from_files)
            {
                // copy each file
                System.IO.FileInfo to_file = new System.IO.FileInfo(string.Format("{0}\\{1}", to.FullName, from_file.Name));
                //Console.WriteLine("Copying File\n->From: {0}\n->To: {1}", from_file.FullName, to_file.FullName);
                CopyAFile(from_file, to_file);
            }
        }

        /// <summary>
        /// Copy a file on the local computer
        /// </summary>
        /// <param name="from">Source</param>
        /// <param name="to">Target</param>
        private static void CopyAFile(System.IO.FileInfo from, System.IO.FileInfo to)
        {
            if (!to.Directory.Exists)
            {
                // if target folder does not exists, create it
                to.Directory.Create();
            }
            if (to.Exists)
            {
                // if the target file already exists, delete it
                to.Delete();
            }
            from.CopyTo(to.FullName);
        }

        /// <summary>
        /// Move a folder on the local computer
        /// </summary>
        /// <param name="from">Source</param>
        /// <param name="to">Target</param>
        private static void MoveAFolder(DirectoryInfo from, DirectoryInfo to)
        {
            // move the directory and any subfolders
            if (from.Root.ToString() != to.Root.ToString())
            {
                // if the directories are not in the same harddrive
                // copy the folder then delete the original folder
                CopyAFolder(from, to);
                from.Delete(true);
            }
            else
            {
                // just use the move command
                from.MoveTo(to.FullName);
            }
        }

        /// <summary>
        /// Move a file on the local computer
        /// </summary>
        /// <param name="from">Source</param>
        /// <param name="to">Target</param>
        private static void MoveAFile(FileInfo from, FileInfo to)
        {
            if (!to.Directory.Exists)
            {
                // if target folder does not exists, create it
                to.Directory.Create();
            }
            if (to.Exists)
            {
                // if the target file already exists, delete it
                to.Delete();
            }

            // move the file
            if (from.Directory.Root.ToString() != to.Directory.Root.ToString())
            {
                // if the directories are not in the same harddrive
                // copy the folder then delete the original folder
                CopyAFile(from, to);
                from.Delete();
            }
            else
            {
                // just use the move command
                from.MoveTo(to.FullName);
            }
        }


        /// <summary>
        /// Get the folder's size
        /// </summary>
        /// <param name="Location">The folder</param>
        /// <returns>The folder's size [bytes]</returns>
        private static long DirectorySize(FileSystemObject Location)
        {
            return DirectorySize(Location, true);
        }

        /// <summary>
        /// Get the folder's size
        /// </summary>
        /// <param name="Location">The folder</param>
        /// <param name="recursive">Include subfolders?</param>
        /// <returns>Folder size [bytes]</returns>
        static long DirectorySize(FileSystemObject Location, bool recursive)
        {
            if (Location.IsRemote)
            {
                // remote, use the server
                if (!ServerConnection.ServerStatus)
                    return 1;

                return ServerFunctions.DirectoryLength(Location.Location, recursive);
            }
            else
            {
                // not remote, so local
                return getFolderLength(recursive, new DirectoryInfo(Location.Location));
            }
        }

        /// <summary>
        /// Get the length of a folder on the local computer
        /// </summary>
        /// <param name="SubFolders">Include subfolders?</param>
        /// <param name="folder">The folder</param>
        /// <returns>The size in bytes</returns>
        private static long getFolderLength(bool SubFolders, DirectoryInfo folder)
        {
            long size = 0;
            if (SubFolders)
            {
                DirectoryInfo[] dirs = folder.GetDirectories();
                foreach (DirectoryInfo di in dirs)
                {
                    size += getFolderLength(SubFolders, di);
                }
            }

            FileInfo[] files = folder.GetFiles();
            foreach (FileInfo fi in files)
            {
                size += fi.Length;
            }
            return size;
        }

        /// <summary>
        /// Get the size of a file system object
        /// </summary>
        /// <param name="Location">The object</param>
        /// <returns>The size</returns>
        public static long Size(FileSystemObject Location)
        {
            if (Location.objectType == FileSystemObjectTypes.File)
            {
                return FileSize(Location);
            }
            else
            {
                return DirectorySize(Location);
            }
        }

        /// <summary>
        /// Return a string representing the size of a file
        /// </summary>
        /// <param name="size">Length of file in bytes</param>
        /// <returns></returns>
        public static string SizeString(long size)
        {
            if (size < 0)
                return string.Empty;

            if (size < 1024)
                return size + " B";

            long KB = size / 1024;
            if (KB < 1024)
                return KB + " KB";

            double MB = ((double)KB) / 1024.0;
            if (MB < 1024)
                return string.Format("{0} {1}", MB.ToString("N2"), "MB");

            double GB = MB / 1024.0;
            if (GB < 1024)
                return string.Format("{0} {1}", GB.ToString("N2"), "GB");

            double TB = GB / 1024.0;
            return string.Format("{0} {1}", TB.ToString("N2"), "TB");
        }

        /// <summary>
        /// Get the file size
        /// </summary>
        /// <param name="Location">The file</param>
        /// <returns>File size [bytes]</returns>
        private static long FileSize(FileSystemObject Location)
        {
            if (Location.IsRemote)
            {
                // remote, use server
                if (!ServerConnection.ServerStatus)
                    return -1;

                return ServerFunctions.FileLength(Location.Location);
            }
            else
            {
                // so the file is local
                FileInfo fi = new FileInfo(Location.Location);
                if (fi.Exists)
                {
                    return fi.Length;
                }
                else
                {
                    return 0;
                }
            }
        }

        /// <summary>
        /// Get a listing of all the subdirectories for this folder
        /// </summary>
        /// <param name="Location">The Directory</param>
        /// <returns>String of subfolders</returns>
        public static string[] Directories(FileSystemObject Location)
        {
            if (Location.IsMyComputer)
                return new string[0];

            if (Location.IsRemote)
            {
                // use the server
                if (!ServerConnection.ServerStatus)
                    return new string[0];

                // folder location exists so proceed
                return ServerFunctions.GetSubDirectoriesS(Location.Location);
            }
            else
            {
                // local
                return GetSubDirectoriesS(Location.Location);
            }
        }


        /// <summary>
        /// Get the sub directories for the directory provided
        /// </summary>
        /// <param name="Directory">The target directory</param>
        /// <returns>Array of the sub directories</returns>
        private static string[] GetSubDirectoriesS(string Directory)
        {
            DirectoryInfo directory = new DirectoryInfo(Directory);
            DirectoryInfo[] directories = directory.GetDirectories();

            List<string> stringDirectory = new List<string>();
            foreach (DirectoryInfo di in directories)
            {
                if ((di.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                {
                    // folder is hidden
                }
                else
                {
                    string location = di.FullName;
                    stringDirectory.Add(location);
                }
            }
            return stringDirectory.ToArray();
        }

        /// <summary>
        /// Get a listing of the files in the directory
        /// </summary>
        /// <param name="Location">The directory</param>
        /// <returns>The files in this directory</returns>
        public static string[] Files(FileSystemObject Location)
        {
            if (Location.IsRemote)
            {
                // remote so use the server
                if (!ServerConnection.ServerStatus)
                    return new string[0];

                // folder location exists so proceed
                return ServerFunctions.GetDirectoryFilesS(Location.Location);
            }
            else
            {
                // use local computer
                return GetDirectoryFilesS(Location.Location);
            }
        }

        /// <summary>
        /// Get the files and sizes for a directory.
        /// </summary>
        /// <param name="Location"></param>
        /// <returns></returns>
        public static string[] FilesAndSizes(FileSystemObject Location)
        {
            if (Location.IsRemote)
            {
                // remote so use the server
                if (!ServerConnection.ServerStatus)
                    return new string[0];

                // folder location exists so proceed
                return ServerFunctions.GetDirectoryFilesAndSizes(Location.Location);
            }
            else
            {
                // use local computer
                return GetDirectoryFilesAndSizes(Location.Location);
            }
        }

        /// <summary>Get directory files and sizes</summary>
        /// <param name="dir"></param>
        /// <returns></returns>
        static string[] GetDirectoryFilesAndSizes(string dir)
        {
            try
            {
                DirectoryInfo directory = new DirectoryInfo(dir);
                FileInfo[] files = directory.GetFiles();

                List<string> stringFiles = new List<string>();
                foreach (FileInfo fi in files)
                {
                    if ((fi.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                    {
                        // file is hidden
                    }
                    else
                    {
                        string location = string.Format("{0}?{1}", fi.FullName, fi.Length);
                        stringFiles.Add(location);
                    }
                }
                return stringFiles.ToArray();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return new string[0];
            }
        }

        /// <summary>
        /// Get the files in the directory
        /// </summary>
        /// <param name="folder">The directory</param>
        /// <returns>Array of full file names</returns>
        private static string[] GetDirectoryFilesS(string Directory)
        {
            DirectoryInfo directory = new DirectoryInfo(Directory);
            FileInfo[] files = directory.GetFiles();

            List<string> stringFiles = new List<string>();
            foreach (FileInfo fi in files)
            {
                if ((fi.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                {
                    // file is hidden
                }
                else
                {
                    string location = fi.FullName;
                    stringFiles.Add(location);
                }
            }
            return stringFiles.ToArray();
        }

        /// <summary>
        /// Get the HDDS
        /// </summary>
        /// <param name="IsServer">For the server?</param>
        /// <returns>The HDDs</returns>
        public static string[] HDDs(bool IsServer)
        {
            if (IsServer)
            {
                // use the server connection
                if (!ServerConnection.ServerStatus)
                    return new string[0];

                return ServerFunctions.GetHDDs();
            }
            else
            {
                // use the local computer
                return GetHDDs();
            }
        }

        /// <summary>
        /// Get the HDDs on the computer
        /// </summary>
        /// <returns>The HDDS</returns>
        private static string[] GetHDDs()
        {
            DriveInfo[] driveInfo = DriveInfo.GetDrives();
            List<string> HDDs = new List<string>();
            foreach (DriveInfo drive in driveInfo)
            {
                if (drive.DriveType == DriveType.Fixed)
                {
                    string Drive = drive.Name;
                    HDDs.Add(Drive);
                }
            }
            return HDDs.ToArray();
        }

        /// <summary>
        /// Check if a location exists
        /// </summary>
        /// <param name="Location">The location to check</param>
        /// <returns>Exists?</returns>
        public static bool Exists(FileSystemObject Location)
        {
            if (Location.IsRemote)
            {
                // use server connection
                if (!ServerConnection.ServerStatus)
                    return false;

                ServerFunctions.SetLocation(Location.Location);
                return ServerFunctions.Exists();
            }
            else
            {
                // use local
                if (Location.objectType == FileSystemObjectTypes.Directory)
                {
                    DirectoryInfo di = new DirectoryInfo(Location.Location);
                    return di.Exists;
                }
                else if (Location.objectType == FileSystemObjectTypes.File)
                {
                    FileInfo fi = new FileInfo(Location.Location);
                    return fi.Exists;
                }
                else
                {
                    throw new NotSupportedException("Does not support object type: " + Location.objectType.ToString() + ". Client.FileActions.Exists()");
                }
            }
        }

        /// <summary>
        /// Create a directory
        /// </summary>
        /// <param name="Location">Where?</param>
        /// <returns>Successful?</returns>
        public static void CreateDirectory(FileSystemObject Location)
        {
            CreateDirectory(Location, true);
        }

        /// <summary>
        /// Create a directory
        /// </summary>
        /// <param name="Location">Where?</param>
        /// <param name="Refresh">Should the directories be automatically refreshed?</param>
        public static void CreateDirectory(FileSystemObject Location, bool Refresh)
        {
            if (Location.IsRemote)
            {
                // on server
                if (!ServerConnection.ServerStatus)
                    return;

                ServerFunctions.CreateDirectory(Location.Location);
            }
            else
            {
                // local
                DirectoryInfo di = new DirectoryInfo(Location.Location);
                if (!di.Exists)
                {
                    // does not currently exist
                    // so make it
                    di.Create();
                }
            }

            if (Refresh)
                FileActionsQueue.Trigger_FileActionCompleted(new FileActionData(ActionTypes.CreateDirectory, Location));
        }

        /// <summary>
        /// Must be on same machine or else will fail
        /// </summary>
        /// <param name="source">Source object</param>
        /// <param name="target">Target object</param>
        public static void Rename(FileSystemObject source, FileSystemObject target)
        {
            if (source.IsRemote != target.IsRemote)
            {
                throw new NotSupportedException("FileActions.Rename() does not work on file actions across different computeres");
            }

            if (source.IsRemote)
            {
                // remote rename instantly
                ServerFunctions.Rename(source.Location, target.Location);
            }
            else
            {
                // local rename
                Move(source, target);
            }
        }
        /// <summary>
        /// Rename an object to a new name
        /// </summary>
        /// <param name="source">Source object</param>
        /// <param name="NewName">New name</param>
        /// <returns>The new filename</returns>
        public static FileSystemObject Rename(FileSystemObject source, string NewName)
        {
            FileSystemObject NewLocation = null;
            if (source.objectType == FileSystemObjectTypes.Directory)
            {
                // rename directory
                DirectoryInfo di = new DirectoryInfo(source.Location);

                string newDirectoryName = string.Format("{0}\\{1}", di.Parent.FullName, NewName);
                NewLocation = new FileSystemObject(newDirectoryName, source.IsRemote, FileSystemObjectTypes.Directory);
            }
            else if (source.objectType == FileSystemObjectTypes.File)
            {
                // rename file
                FileInfo fi = new FileInfo(source.Location);

                string newFileName = string.Format("{0}\\{1}", fi.Directory.FullName, NewName);
                NewLocation = new FileSystemObject(newFileName, source.IsRemote, FileSystemObjectTypes.File);
            }

            if (NewLocation != null)
            {
                Rename(source, NewLocation);
            }
            return NewLocation;
        }
    }
}
